﻿#region Using Statements
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Lidgren.Network;
using System.Threading;
using System.Net;
#endregion

namespace BreakOutBattles.IO.Net
{
    public class GameNetClient
    {
        #region Delegates and Events
        public delegate void HostConfigurationReceivedHandler(object sender, MatchConfiguration args);
        public delegate void ClientConfigReceivedHandler(object sender, JoinClientConfig config);
        public delegate void ReadyFlagReceivedHandler(object sender, object args);
        public delegate void JoinRequestReceivedHandler(object sender, object args);
        public delegate void PaddleSyncReceivedHandler(object sender, object args);
        public delegate void BallSyncReceivedHandler(object sender, object args);
        public delegate void ConnectedToServerHandler(object sender, object args);

        public event HostConfigurationReceivedHandler HostConfigurationReceived;
        public event ClientConfigReceivedHandler ClientConfigReceived;
        public event ReadyFlagReceivedHandler ReadyFlagReceived;
        public event JoinRequestReceivedHandler JoinRequestReceived;
        public event PaddleSyncReceivedHandler PaddleSyncReceived;
        public event BallSyncReceivedHandler BallSyncReceived;
        public event ConnectedToServerHandler ConnectedToServer;
        #endregion

        #region Fields
        private const string DEFAULT_IP = "localhost";
        private const int DEFAULT_PORT = 8989;

        private NetClient client;
        private NetPeerConfiguration configuration;
        private Thread receiveMessageThread;
        #endregion

        #region Constructors
        /// <summary>
        /// Creates a semi-initiliazied client. Should be configurated manually
        /// before connecting to a server.
        /// </summary>
        public GameNetClient()
        { }

        /// <summary>
        /// Creates a new game client with default settings.
        /// </summary>
        /// <param name="name">A unique name used to distinguish different clients.</param>
        public GameNetClient(string name)
        {
            configuration = new NetPeerConfiguration(name);
            configuration.EnableMessageType(NetIncomingMessageType.DiscoveryResponse);
            client = new NetClient(configuration);
            client.RegisterReceivedCallback(new SendOrPostCallback(MessageReceiver));
        }
        #endregion

        #region Event Handling
        /// <summary>
        /// Fires an event indicating that a connection to the game server was
        /// succeded.
        /// </summary>
        private void FireConnectedToServerEvent()
        {
            if (ConnectedToServer != null)
            {
                ConnectedToServer(this, null);
            }
        }

        /// <summary>
        /// Fires an event indicating that a host configuration has
        /// just been received.
        /// </summary>
        /// <param name="config">The configuration to send to the event handlers.</param>
        private void FireHostConfigReceived(MatchConfiguration config)
        {
            if (HostConfigurationReceived != null)
            {
                HostConfigurationReceived(this, config);
            }
        }

        /// <summary>
        /// Fires an event indicating that a joining client configuration
        /// just been received.
        /// </summary>
        /// <param name="config">The configuration to send to the event handlers.</param>
        private void FireClientConfigReceived(JoinClientConfig config)
        {
            if (ClientConfigReceived != null)
            {
                ClientConfigReceived(this, config);
            }
        }

        /// <summary>
        /// Fires an event indicating that a join request just been
        /// received.
        /// </summary>
        /// <param name="args"></param>
        private void FireJoinRequestReceived(object args)
        {
            if (JoinRequestReceived != null)
            {
                JoinRequestReceived(this, args);
            }
        }

        /// <summary>
        /// Fires an event indicating that a ready flag just been
        /// received.
        /// </summary>
        /// <param name="args"></param>
        private void FireReadyFlagReceived(object args)
        {
            if (ReadyFlagReceived != null)
            {
                ReadyFlagReceived(this, args);
            }
        }

        /// <summary>
        /// Fires an event indicating that paddle sync data just been
        /// received.
        /// </summary>
        /// <param name="args"></param>
        private void FirePaddleSyncReceived(object args)
        {
            if (PaddleSyncReceived != null)
            {
                PaddleSyncReceived(this, args);
            }
        }

        /// <summary>
        /// Fires an event indicating that ball sync data just been
        /// received.
        /// </summary>
        /// <param name="args"></param>
        private void FireBallSyncReceived(object args)
        {
            if (BallSyncReceived != null)
            {
                BallSyncReceived(this, args);
            }
        }
        #endregion

        #region Message Handling
        /// <summary>
        /// Checks what type of game data a received network message contains, depending
        /// on the type of game data different events will be fired to notify the handlers 
        /// about the received data.
        /// </summary>
        /// <param name="message">A message that contains the game data.</param>
        private void HandleReceivedGameData(NetIncomingMessage message)
        {
            GameDataType gameDataType = (GameDataType)message.ReadByte();

            switch (gameDataType)
            {
                case GameDataType.CLIENT_CONFIG_DATA:
                    FireJoinRequestReceived(null);
                    break;

                case GameDataType.HOST_CONFIG_DATA:
                    MatchConfiguration matchConfig = new MatchConfiguration();
                    matchConfig.HostPlayerName = message.ReadString();
                    matchConfig.LevelName = message.ReadString();
                    matchConfig.LifesPerRound = message.ReadUInt16();

                    FireHostConfigReceived(matchConfig);
                    break;

                case GameDataType.JOIN_REQUEST_DATA:
                    FireJoinRequestReceived(null);
                    break;

                case GameDataType.READY_FLAG_DATA:
                    FireReadyFlagReceived(null);
                    break;

                case GameDataType.PADDLE_SYNC_DATA:
                    FirePaddleSyncReceived(null);
                    break;

                case GameDataType.BALL_SYNC_DATA:
                    break;
            }
        }

        /// <summary>
        /// Read incoming messages from the game server.
        /// </summary>
        private void MessageReceiver(object peer)
        {
            NetIncomingMessage message;
            while ((message = client.ReadMessage()) != null)
            {
                switch (message.MessageType)
                {
                    case NetIncomingMessageType.DebugMessage:
                        //Console.WriteLine(message.ReadString());
                        break;
                    /*case NetIncomingMessageType.StatusChanged:
                        NetConnectionStatus status = (NetConnectionStatus)message.ReadByte();
                        if (status == NetConnectionStatus.RespondedConnect)
                        {
                        }
                        break;*/
                    case NetIncomingMessageType.DiscoveryResponse:
                        client.Connect(message.SenderEndpoint);
                        FireConnectedToServerEvent();
                        Console.WriteLine("Connected to: " + message.SenderEndpoint.ToString());
                        break;
                    case NetIncomingMessageType.Data:
                        HandleReceivedGameData(message);
                        break;
                }
            }
        }

        /// <summary>
        /// Sends a host configuration message to the game server.
        /// </summary>
        /// <param name="config">Match configuration to send.</param>
        public void SendHostConfigMessage(MatchConfiguration config)
        {
            NetOutgoingMessage hostConfigMessage = client.CreateMessage();
            hostConfigMessage.Write((byte)GameDataType.HOST_CONFIG_DATA);
            hostConfigMessage.Write(config.HostPlayerName);
            hostConfigMessage.Write(config.LevelName);
            hostConfigMessage.Write((uint)config.LifesPerRound);

            client.SendMessage(hostConfigMessage, NetDeliveryMethod.ReliableOrdered);
        }

        /// <summary>
        /// Sends a client configuration message to the game server.
        /// </summary>
        /// <param name="config">Client configuration to send.</param>
        public void SendJoinClientConfigMessage(JoinClientConfig config)
        {
            NetOutgoingMessage joinClientConfig = client.CreateMessage();
            joinClientConfig.Write((byte)GameDataType.CLIENT_CONFIG_DATA);
            joinClientConfig.Write(config.PlayerName);

            client.SendMessage(joinClientConfig, NetDeliveryMethod.ReliableOrdered);
        }

        /// <summary>
        /// Sends a join request message to the game server.
        /// </summary>
        public void SendJoinRequestMessage()
        {
            NetOutgoingMessage joinRequestMessage = client.CreateMessage();
            joinRequestMessage.Write((byte)GameDataType.JOIN_REQUEST_DATA);

            client.SendMessage(joinRequestMessage, NetDeliveryMethod.ReliableOrdered);
        }

        /// <summary>
        /// Sends a "ready flag" message to the game server. 
        /// </summary>
        public void SendReadyMessage()
        {
            NetOutgoingMessage readyMessage = client.CreateMessage();
            readyMessage.Write((byte)GameDataType.READY_FLAG_DATA);

            client.SendMessage(readyMessage, NetDeliveryMethod.ReliableOrdered);
        }
        #endregion

        /// <summary>
        /// Attempts to connect to the game server.
        /// </summary>
        /// <param name="localConnection">A boolean indicating if the client is trying to connect to a local server.</param>
        /// <param name="host">The ip of the server to connect.</param>
        /// <param name="port">The port of the server to connect.</param>
        public void Connect(bool localConnection = true, string host = DEFAULT_IP, int port = DEFAULT_PORT)
        {
            client.Start();

            if (localConnection)
            {
                client.DiscoverLocalPeers(port);
            }
            else
            {
                client.DiscoverKnownPeer(host, port);
            }
        }

        /// <summary>
        /// Attempts to disconnect from a game server, if a connection is active.
        /// </summary>
        public void Disconnect()
        {
            client.Disconnect("Disconnected");
        }
    }
}
